/*
 * This file is part of b.AMP.
 *
 * b.AMP is free software: you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 *
 * b.AMP is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with b.AMP.  If not, see <http://www.gnu.org/licenses/>.
 */

#include "async_loop.hpp"
#include <boost/thread.hpp>


/*
 * Implementation.
 */

struct bamp::AsyncLoop::Implementation
{

    Implementation()
        : thread  (),
          mutex   (),
          running (false),
          stopping(false)
    {
        // Nothing here
    }


    boost::thread thread;   /// Loop thread
    boost::mutex  mutex;    /// Mutex for internal state
    bool          running;  /// Whether the thread is running
    bool          stopping; /// Whether the thread is stopping
};



/*
 * Public interface.
 */


bamp::AsyncLoop::AsyncLoop()
    : pimpl(new Implementation())
{
    // Nothing here
}


bamp::AsyncLoop::~AsyncLoop()
{
    // Interrupt the thread. We cannot just call stop() and waitStop()
    // here since this would require calling virtual functions in a
    // destructor.
    pimpl->thread.interrupt();
}


bool bamp::AsyncLoop::run()
{
    boost::mutex::scoped_lock lock(pimpl->mutex);

    if (!pimpl->running && !pimpl->stopping) {
        if (!preRun()) {
            return pimpl->running;
        }

        try {
            pimpl->running = true;
            pimpl->thread  = boost::thread(&bamp::AsyncLoop::loop, this);
        } catch(const boost::thread_resource_error& e) {
            pimpl->running = false;
        }
    }

    return pimpl->running;
}


void bamp::AsyncLoop::stop()
{
    boost::mutex::scoped_lock lock(pimpl->mutex);

    if (pimpl->running) {

        if (!preStop()) {
            return;
        }
        
        // Tell the thread to stop as soon as possible
        pimpl->running  = false;
        pimpl->stopping = true;
    }
}


void bamp::AsyncLoop::waitStop()
{
    pimpl->thread.join();    
}


bool bamp::AsyncLoop::isRunning()
{
    boost::mutex::scoped_lock lock(pimpl->mutex);
    return pimpl->running;
}


bool bamp::AsyncLoop::isStopping()
{
    boost::mutex::scoped_lock lock(pimpl->mutex);
    return pimpl->stopping;
}



/*
 * Private methods.
 */


void bamp::AsyncLoop::loop()
{
    // Run postRun() once. If it fails, don't run the loop at all.
    {
        boost::mutex::scoped_lock lock(pimpl->mutex);
        
        if (!postRun()) {
            pimpl->running  = false;
            pimpl->stopping = false;
            return;
        }
    }

    // Run the loop until stopping has been requested.
    while (true) {

        // Stop if stopping has been requested
        {
            boost::mutex::scoped_lock lock(pimpl->mutex);
            if (!pimpl->running) break;
        }

        // Run an iteration
        if (!iteration()) break;
    }

    // Mark the thread as stopped.
    {
        boost::mutex::scoped_lock lock(pimpl->mutex);
        pimpl->running  = false;
        pimpl->stopping = false;
        postStop();
    }
}
